package domain.grammar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import domain.regularExpression.NonUnaryRegularExpressionOperator;
import domain.regularExpression.RegularExpressionElement;
import domain.regularExpression.UnaryRegularExpressionOperator;


public class ExtendedGrammar {
	private Set<ExtendedNonTerminalSymbol> _nonTerminals;
	private Set<ExtendedTerminalSymbol> _terminals;
	private ExtendedProductionSet _productions;
	private ExtendedNonTerminalSymbol _mainSymbol;
	private Map<RegularExpressionElement,Boolean> _nullableDicc;
	private Map<RegularExpressionElement,Set<ExtendedTerminalSymbol>> _firstsDicc;
	private Map<RegularExpressionElement,Set<ExtendedTerminalSymbol>> _nextsDicc;
	
	
	
	
	
	public ExtendedGrammar(Set<ExtendedNonTerminalSymbol> nonTerminals,
			Set<ExtendedTerminalSymbol> terminals,
			ExtendedProductionSet productions, ExtendedNonTerminalSymbol symbol) {
		super();
		_nonTerminals = nonTerminals;
		_terminals = terminals;
		_productions = productions;
		_mainSymbol = symbol;
		setIds();
		NullableCalculator nullCalculator = new NullableCalculator(this);
		_nullableDicc = nullCalculator.calculateNullables();
		ExtGrammarCalculator grammCalculator = new ExtGrammarCalculator(this);
		_firstsDicc = grammCalculator.calculateFirsts(_nullableDicc);
		_nextsDicc = grammCalculator.calculateNexts(_nullableDicc, _firstsDicc);
	}
	
	
	
	
	private void setIds() {
		_mainSymbol.setId(1);
		setNTIdInProductions(_mainSymbol);
		int idCounter = 2;
		Iterator<ExtendedNonTerminalSymbol> itNonTerm = _nonTerminals.iterator();
		while(itNonTerm.hasNext()){
			ExtendedNonTerminalSymbol anExtNT = itNonTerm.next();
			if(!_mainSymbol.equals(anExtNT)){
				anExtNT.setId(idCounter);
				setNTIdInProductions(anExtNT);
				idCounter++;
			}
		}
		
		Iterator<ExtendedProduction> itProd = _productions.get_productions().iterator();
		while(itProd.hasNext()){
			ExtendedProduction extProd = itProd.next();
			List<RegularExpressionElement> ntInRegExp = extProd.get_rightSide().getListOfElemOfClass(RegularExpressionElement.class);
			Iterator<RegularExpressionElement> itNTInRegExp = ntInRegExp.iterator();
			while(itNTInRegExp.hasNext()){
				
				RegularExpressionElement aNodeOfProd = itNTInRegExp.next();
				if(!(aNodeOfProd instanceof ExtendedNonTerminalSymbol)){
					aNodeOfProd.setId(idCounter);
					idCounter++;
				}
				
			}
		}
	}
	
	private void setNTIdInProductions(ExtendedNonTerminalSymbol anExtNT){
		Iterator<ExtendedProduction> itProd = _productions.get_productions().iterator();
		while(itProd.hasNext()){
			ExtendedProduction extProd = itProd.next();
			List<ExtendedNonTerminalSymbol> ntInRegExp = extProd.get_rightSide().getListOfElemOfClass(ExtendedNonTerminalSymbol.class);
			Iterator<ExtendedNonTerminalSymbol> itNTInRegExp = ntInRegExp.iterator();
			while(itNTInRegExp.hasNext()){
				ExtendedNonTerminalSymbol anExtNTinRE = itNTInRegExp.next();
				if(anExtNTinRE.getName().equals(anExtNT.getName())){
					anExtNTinRE.setId(anExtNT.getId());
				}
			}
		}
	}




	public Set<ExtendedNonTerminalSymbol> get_nonTerminals() {
		return _nonTerminals;
	}
	public Set<ExtendedTerminalSymbol> get_terminals() {
		return _terminals;
	}
	public ExtendedProductionSet get_productions() {
		return _productions;
	}
	public ExtendedNonTerminalSymbol get_mainSymbol() {
		return _mainSymbol;
	}
	
	
	public Map<RegularExpressionElement, Boolean> get_nullableDicc() {
		return _nullableDicc;
	}


	public Map<RegularExpressionElement, Set<ExtendedTerminalSymbol>> get_firstsDicc() {
		return _firstsDicc;
	}


	public Map<RegularExpressionElement, Set<ExtendedTerminalSymbol>> get_nextsDicc() {
		return _nextsDicc;
	}




	public boolean nullable(RegularExpressionElement ree){
		return this.get_nullableDicc().get(ree);
	}
	

	
	
	public Set<ExtendedTerminalSymbol> firsts(RegularExpressionElement ree){
		return this.get_firstsDicc().get(ree);
	}
	
	public Set<ExtendedTerminalSymbol> nexts(RegularExpressionElement ree){
		return this.get_nextsDicc().get(ree);
	}
	

	
	
	public String toString(){
		return 	"<"+showSet(this._nonTerminals)+"; "
				+showSet(this._terminals)+"; P; "
				+this._mainSymbol.toString()+">\n"
				+"where P is:\n"
				+this._productions.toString();
	}
	
	private String showSet(Set<?> aSet){
		String result = "{";
		boolean isFirstIt = true;
		Iterator<?> itSet = aSet.iterator();
		while(itSet.hasNext()){
			Object anElem = itSet.next();
			if(!isFirstIt){
				result = result +", ";
			}
			result = result + anElem.toString();
			isFirstIt = false;
		}
		result = result + "}";
		return result;
	}
	
	
	public Set<RegularExpressionElement> getElementsWithConflict(){
		Set<RegularExpressionElement> elementsWithConflict = new HashSet<RegularExpressionElement>();
		Set<NonUnaryRegularExpressionOperator> nonUnaryOpSet = getElementsInstanceOf(NonUnaryRegularExpressionOperator.class);
		Iterator<NonUnaryRegularExpressionOperator> nonUnOpIt = nonUnaryOpSet.iterator();
		while(nonUnOpIt.hasNext()){
			NonUnaryRegularExpressionOperator aNonUnOp = nonUnOpIt.next();
			if(aNonUnOp.isPipe()){
				if(hasConflict(aNonUnOp)){
					elementsWithConflict.add(aNonUnOp);
				}
			}
		}
		
		Set<UnaryRegularExpressionOperator> unaryOpSet = getElementsInstanceOf(UnaryRegularExpressionOperator.class);
		Iterator<UnaryRegularExpressionOperator> unOpIt = unaryOpSet.iterator();
		while(unOpIt.hasNext()){
			UnaryRegularExpressionOperator aUnOp = unOpIt.next();
			if(hasConflict(aUnOp)){
				elementsWithConflict.add(aUnOp);
			}
		}
		return elementsWithConflict;
	}
	
	
	public boolean hasConflict(){
		Set<NonUnaryRegularExpressionOperator> nonUnaryOpSet = getElementsInstanceOf(NonUnaryRegularExpressionOperator.class);
		Iterator<NonUnaryRegularExpressionOperator> nonUnOpIt = nonUnaryOpSet.iterator();
		while(nonUnOpIt.hasNext()){
			NonUnaryRegularExpressionOperator aNonUnOp = nonUnOpIt.next();
			if(aNonUnOp.isPipe()){
				if(hasConflict(aNonUnOp)){
					return true;
				}
			}
		}
		
		Set<UnaryRegularExpressionOperator> unaryOpSet = getElementsInstanceOf(UnaryRegularExpressionOperator.class);
		Iterator<UnaryRegularExpressionOperator> unOpIt = unaryOpSet.iterator();
		while(unOpIt.hasNext()){
			UnaryRegularExpressionOperator aUnOp = unOpIt.next();
			if(hasConflict(aUnOp)){
				return true;
			}
		}
		return false;
	}
	
	
	
	private boolean hasConflict(UnaryRegularExpressionOperator unOp) {
		RegularExpressionElement subElem = unOp.getSubElements().get(0);
		Map<RegularExpressionElement,Set<ExtendedTerminalSymbol>> nextsDicc = this.get_nextsDicc();
		Set<ExtendedTerminalSymbol> subElemFirsts = firsts(subElem);
		Set<ExtendedTerminalSymbol> subElemNexts = nextsDicc.get(subElem);
		return (containsAny(transformToString(subElemFirsts),transformToString(subElemNexts))||nullable(subElem));
	}




	private boolean hasConflict(NonUnaryRegularExpressionOperator aNonUnOp){
		if(aNonUnOp.isPipe()){
			Set<String> tempSet = new HashSet<String>();
			Map<RegularExpressionElement,Set<ExtendedTerminalSymbol>> nextsDicc = this.get_nextsDicc();
			List<RegularExpressionElement> subElems = aNonUnOp.getSubElements();
			Iterator<RegularExpressionElement> itSubElems = subElems.iterator();
			while(itSubElems.hasNext()){
				RegularExpressionElement aSubElem = itSubElems.next();
				Set<ExtendedTerminalSymbol> aSubElemDSs = directiveSymbols(aSubElem,nextsDicc.get(aSubElem));
				Set<String> aSubElemStrDSs = transformToString(aSubElemDSs);
				if(containsAny(tempSet,aSubElemStrDSs)){
					return true;
				}else{
					tempSet.addAll(aSubElemStrDSs);
				}
			}
			return false;
		}else{
			return false;
		}
	}
	
	
	private Set<ExtendedTerminalSymbol> directiveSymbols(RegularExpressionElement aRee, Set<ExtendedTerminalSymbol> nexts){
		Set<ExtendedTerminalSymbol> directiveSymbols = new HashSet<ExtendedTerminalSymbol>();
		directiveSymbols.addAll(firsts(aRee));
		if(nullable(aRee)){
			directiveSymbols.addAll(nexts);
		}
		return directiveSymbols;
	}
	
	
	
	
	private boolean containsAny(Set set1, Set set2) {
		Iterator itSet2 = set2.iterator();
		while(itSet2.hasNext()){
			if(set1.contains(itSet2.next())){
				return true;
			}
		}
		return false;
	}




	private Set<String> transformToString(Set<ExtendedTerminalSymbol> aSet) {
		Set<String> result = new HashSet();
		Iterator<ExtendedTerminalSymbol> itSet = aSet.iterator();
		while(itSet.hasNext()){
			result.add(itSet.next().toString());
		}
		return result;
	}




	private Set getElementsInstanceOf(Class<?> aClass){
		Set result = new HashSet();
		Iterator<ExtendedProduction> itProd = _productions.get_productions().iterator();
		while(itProd.hasNext()){
			ExtendedProduction extProd = itProd.next();
			if(aClass.isInstance(extProd.get_leftSide())){
				result.add(extProd.get_leftSide());
			}
			result.addAll(extProd.get_rightSide().getSetOfElemOfClass(aClass));
		}
		return result;
	}
	
}
